package com.qd.panda.service.stroke.order;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.math.Money;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.db.BaseService;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.DateUtil;
import com.cdqidi.util.JacksonUtil;
import com.cdqidi.util.RedisTemplateUtil;
import com.cdqidi.util.ValidatorUtil;
import com.cdqidi.valid.group.DefaultGroup;
import com.cdqidi.valid.group.UpdateGroup;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.qd.common.panda.domain.entity.order.OrderDTO;
import com.qd.common.panda.domain.entity.order.OrderQueryDTO;
import com.qd.common.panda.domain.entity.order.TotalPreferentialAmountDTO;
import com.qd.common.panda.domain.entity.stroke.*;
import com.qd.common.panda.domain.entity.stroke.preferential.UserStrokeOrderPreferentialLogDTO;
import com.qd.common.panda.domain.enums.GasStationCompanyNameEnum;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.panda.dc.DcService;
import com.qd.panda.dc.dto.charge.ChargeActionReqDTO;
import com.qd.panda.dc.dto.charge.StatusChargeRespDTO;
import com.qd.panda.mapper.stroke.UserStrokeOrderMapper;
import com.qd.panda.model.CarPark;
import com.qd.panda.model.UserStrokeOrder;
import com.qd.panda.service.park.CarParkService;
import com.qd.panda.service.space.ParkSpaceTenantService;
import com.qd.panda.service.stroke.BigDecimalUtil;
import com.qd.panda.service.stroke.DisplayUtil;
import com.qd.system.service.dict.MbUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

import static java.util.stream.Collectors.toList;

/**
 * 用户订单
 *
 * @author sjk
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class UserStrokeOrderService extends BaseService<UserStrokeOrderMapper, UserStrokeOrder> {

    private static final String PAY_STATUS = "pay_status";
    private static final String START_TIME = "start_time";
    private static final String END_TIME = "end_time";
    private static final String PREFIX_ID = "panda_user_stroke_order_orderId_{0}";
    private static final String PREFIX_UNIQUE_THIRD_PARTY_ORDER_ID = "panda_user_stroke_order_thirdPartyOrderId_{0}";

    private final RedisTemplateUtil<UserStrokeOrder> redisTemplateUtil;

    private final CarParkService carParkService;
    private final UserStrokeOrderChargeService userStrokeOrderChargeService;
    private final UserStrokeOrderGasService userStrokeOrderGasService;
    private final UserStrokeOrderMallService userStrokeOrderMallService;
    private final UserStrokeOrderPreferentialLogService userStrokeOrderPreferentialLogService;
    private final ParkSpaceTenantService parkSpaceTenantService;
    private final UserStrokeOrderRefundService userStrokeOrderRefundService;
    private final DcService dcService;
    private final ValidatorUtil validatorUtil;
    private final ObjectMapper objectMapper;

    /**
     * 添加或者更新
     *
     * @param dto 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdate(UserStrokeOrderDTO dto) {
        if (Boolean.TRUE.equals(isExistsById(dto.getOrderId()))) {
            validatorUtil.checkValid(dto, DefaultGroup.class, UpdateGroup.class);
            update(dto);
        } else {
            validatorUtil.checkValid(dto, DefaultGroup.class);
            save(dto);
        }
        final UserStrokeOrderChargeDTO orderCharge = dto.getOrderCharge();
        if (null != orderCharge) {
            userStrokeOrderChargeService.saveOrUpdate(orderCharge);
        }
    }

    /**
     * 保存
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public UserStrokeOrderDTO save(UserStrokeOrderDTO dto) {
        checkUnique(dto);
        final UserStrokeOrder model = dtoToModel(dto);
        try {
            if (super.save(model)) {
                return modelToDto(model);
            }
            throw new ApiException("添加失败");
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 修改
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public UserStrokeOrderDTO update(UserStrokeOrderDTO dto) {
        checkUnique(dto);
        final UserStrokeOrder historyModel = Optional.ofNullable(getById(dto.getOrderId())).orElseThrow(() -> new ApiException("ID不存在,不能更新"));
        final UserStrokeOrder model = dtoToModel(dto);
        try {
            if (super.updateById(model)) {
                return modelToDto(model);
            }
            throw new ApiException("更新失败");
        } finally {
            clearCache(historyModel);
            clearCache(model);
            historyModel.freeData();
            model.freeData();
        }
    }

    /**
     * 删除，单个
     *
     * @param orderId 主键
     */
    @Transactional(rollbackFor = Exception.class)
    public void removeById(String orderId) {
        final UserStrokeOrder model = Optional.ofNullable(getById(orderId)).orElseThrow(() -> new ApiException("数据不存在,不能删除"));
        try {
            if (!super.removeById(orderId)) {
                throw new ApiException("删除失败");
            }
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 根据传入对象构建查询参数，判断是否存在
     *
     * @param dto 参数
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExists(UserStrokeOrderDTO dto) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = createQueryWrapper(dto);
        final boolean exists = super.count(queryWrapper) > 0;
        queryWrapper.clear();
        return exists;
    }

    /**
     * 获得免费停车时长，如果已经有一次停车记录了，那么免费时长就为0
     *
     * @param parkId 停车场ID
     * @param sid    行程ID
     * @return 免费时长
     */
    @Transactional(readOnly = true)
    public Integer getFreeTime(String parkId, String sid) {
        final CarPark carPark = Optional.ofNullable(carParkService.getById(parkId)).orElseThrow(() -> new ApiException("停车场ID不存在"));
        int freeTime = Optional.ofNullable(carPark.getIntoFreeTime()).orElse(0);
        UserStrokeOrderDTO query = new UserStrokeOrderDTO();
        query.setSid(sid);
        query.setTripType(DisplayUtil.TRIP_TYPE_0);
        final Boolean exists = isExists(query);
        query.freeData();
        if (Boolean.TRUE.equals(exists)) {
            freeTime = 0;
        }
        return freeTime;
    }

    /**
     * 根据主键判断数据是否存在
     *
     * @param orderId 主键
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExistsById(String orderId) {
        boolean exists = false;
        if (StringUtils.hasLength(orderId)) {
            final UserStrokeOrder model = getById(orderId);
            exists = (null != model);
            if (exists) {
                model.freeData();
            }
        }
        return exists;
    }

    /**
     * 根据主键获取数据
     *
     * @param orderId 主键
     * @return 单条记录
     */
    @Transactional(readOnly = true)
    public UserStrokeOrder getById(String orderId) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, orderId);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getById(orderId), UserStrokeOrder.class);
    }

    /**
     * 根据主键获取数据
     *
     * @param orderId 主键
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public UserStrokeOrderDTO getByIdDto(String orderId) {
        return Optional.ofNullable(getById(orderId)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录
     */
    @Transactional(readOnly = true)
    public UserStrokeOrder getOne(UserStrokeOrderDTO dto) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = createQueryWrapper(dto);
        UserStrokeOrder model = super.getOne(queryWrapper);
        queryWrapper.clear();
        return model;
    }

    /**
     * 获得支付成功的订单列表
     *
     * @param sid 行程ID
     * @return 当前行程下的订单，支付失败的不算
     */
    public List<UserStrokeOrder> getOkList(String sid, boolean isAsc) {
        QueryWrapper<UserStrokeOrder> query = new QueryWrapper<>();
        query.eq("sid", sid);
        query.in(PAY_STATUS, DisplayUtil.PAY_STATUS_1);
        if (isAsc) {
            query.orderByAsc(END_TIME);
        } else {
            query.orderByDesc(END_TIME);
        }
        final List<UserStrokeOrder> list = getBaseMapper().selectList(query);
        query.clear();
        return list;
    }

    /**
     * 获得订单列表，未支付和支付成功的
     *
     * @param sid 行程ID
     * @return 当前行程下的订单，支付失败的不算
     */
    public List<UserStrokeOrder> getList(String sid, boolean isAsc) {
        QueryWrapper<UserStrokeOrder> query = new QueryWrapper<>();
        query.eq("sid", sid);
        query.in(PAY_STATUS, DisplayUtil.PAY_STATUS_0, DisplayUtil.PAY_STATUS_1);
        if (isAsc) {
            query.orderByAsc(END_TIME);
        } else {
            query.orderByDesc(END_TIME);
        }
        final List<UserStrokeOrder> list = getBaseMapper().selectList(query);
        query.clear();
        return list;
    }

    /**
     * 获取最后一笔未交费订单
     *
     * @param sid 行程ID
     * @return 最后一笔缴费成功的订单
     */
    @Transactional(readOnly = true)
    public UserStrokeOrder getLast(String sid) {
        QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sid", sid);
        queryWrapper.in(PAY_STATUS, DisplayUtil.PAY_STATUS_0, DisplayUtil.PAY_STATUS_3, DisplayUtil.PAY_STATUS_1);
        queryWrapper.in("order_type", DisplayUtil.ORDER_TYPE_1, DisplayUtil.ORDER_TYPE_2);
        queryWrapper.last("limit 1");
        queryWrapper.orderByDesc(END_TIME);
        UserStrokeOrder userStrokeOrder = super.getOne(queryWrapper, false);
        queryWrapper.clear();
        return userStrokeOrder;
    }

    /**
     * 获取最后一笔支付成功的预约订单
     *
     * @param sid 行程ID
     * @return 最后一笔缴费成功的订单
     */
    @Transactional(readOnly = true)
    public UserStrokeOrder getLastReserve(String sid) {

        return getLastReserve(sid, DisplayUtil.PAY_STATUS_1);
    }

    @Transactional(readOnly = true)
    public UserStrokeOrder getLastReserve(String sid, Integer payStatus) {
        QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sid", sid);
        if (null != payStatus) {
            queryWrapper.eq(PAY_STATUS, payStatus);
        }
        queryWrapper.eq("order_type", DisplayUtil.ORDER_TYPE_0);
        queryWrapper.last("limit 1");
        UserStrokeOrder userStrokeOrder = super.getOne(queryWrapper, false);
        queryWrapper.clear();
        return userStrokeOrder;
    }

    /**
     * 查询存在的订单信息
     *
     * @param sid       行程ID
     * @param parkId    停车场ID
     * @param start     订单开始时间
     * @param payAmount 支付金额
     * @return true or false
     */
    @Transactional(readOnly = true)
    public boolean isExistsByOrder(String sid, String parkId, LocalDateTime start, BigDecimal payAmount) {
        QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sid", sid);
        queryWrapper.eq("park_id", parkId);
        queryWrapper.eq(START_TIME, start);
        queryWrapper.eq("pay_amount", payAmount);
        queryWrapper.in(PAY_STATUS, DisplayUtil.PAY_STATUS_0, DisplayUtil.PAY_STATUS_1);
        boolean exists = getBaseMapper().exists(queryWrapper);
        queryWrapper.clear();
        return exists;
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public UserStrokeOrderDTO getOneDto(UserStrokeOrderDTO dto) {
        return Optional.ofNullable(getOne(dto)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据
     */
    @Transactional(readOnly = true)
    public List<UserStrokeOrder> getList(UserStrokeOrderDTO dto) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = createQueryWrapper(dto);
        final List<UserStrokeOrder> list = super.list(queryWrapper);
        queryWrapper.clear();
        return list;
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据，转换成页面对象
     */
    @Transactional(readOnly = true)
    public List<UserStrokeOrderDTO> getListDto(UserStrokeOrderDTO dto) {
        return modelToDtoList(getList(dto));
    }

    /**
     * 分页数据
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */
    @Transactional(readOnly = true)
    public IPage<UserStrokeOrderDTO> page(Page<UserStrokeOrder> page, UserStrokeOrderDTO dto) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = createQueryWrapper(dto, "uo.");
        final IPage<UserStrokeOrderDTO> iPage = baseMapper.page(page, queryWrapper);
        queryWrapper.clear();
        return iPage;
    }


    /**
     * 根据唯一性索引获得数据
     * <p>
     * param thirdPartyOrderId
     *
     * @return 单条数据
     */
    @Transactional(readOnly = true)
    public UserStrokeOrder getUniqueByThirdPartyOrderId(String thirdPartyOrderId) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("third_party_order_id", thirdPartyOrderId);
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_UNIQUE_THIRD_PARTY_ORDER_ID, thirdPartyOrderId);
        final UserStrokeOrder model = redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getOne(queryWrapper, false), UserStrokeOrder.class);
        queryWrapper.clear();
        return model;
    }

    /**
     * 验证唯一性索引
     *
     * @param dto 页面对象
     */
    @Transactional(readOnly = true)
    public void checkUnique(UserStrokeOrderDTO dto) {
        if (StringUtils.hasText(dto.getThirdPartyOrderId())) {
            UserStrokeOrder model = getUniqueByThirdPartyOrderId(dto.getThirdPartyOrderId());
            if (null != model) {
                checkUnique(dto, model);
            }
        }
    }

    /**
     * 验证唯一性索引
     *
     * @param dto   页面对象
     * @param model 根据约束字段查询出来的数据库对象
     */
    private void checkUnique(UserStrokeOrderDTO dto, UserStrokeOrder model) {
        if (!StringUtils.hasLength(dto.getOrderId())) {
            throw new ApiException("数据已存在，请勿重复添加");
        }
        if (!dto.getOrderId().equals(model.getOrderId())) {
            throw new ApiException("数据已存在，请勿重复添加");
        }
    }

    /**
     * 清空缓存
     *
     * @param model 数据库对象
     */
    public void clearCache(UserStrokeOrder model) {
        if (null == model) {
            return;
        }
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getOrderId());
        redisTemplateUtil.deleteByKey(key);
        if (StringUtils.hasText(model.getThirdPartyOrderId())) {
            key = RedisTemplateUtil.getRedisCacheKey(PREFIX_UNIQUE_THIRD_PARTY_ORDER_ID, model.getThirdPartyOrderId());
            redisTemplateUtil.deleteByKey(key);
        }
    }

    /**
     * 数据库对象转页面对象
     *
     * @param model 数据库对象
     * @return 页面对象
     */
    public UserStrokeOrderDTO modelToDto(UserStrokeOrder model) {
        if (null == model) {
            return null;
        }
        final UserStrokeOrderDTO dto = new UserStrokeOrderDTO();
        dto.setOrderId(model.getOrderId());
        dto.setThirdPartyOrderId(model.getThirdPartyOrderId());
        dto.setStartTime(model.getStartTime());
        dto.setEndTime(model.getEndTime());
        dto.setNickName(model.getNickName());
        dto.setOrderAmount(model.getOrderAmount());
        dto.setPayAmount(model.getPayAmount());
        dto.setOnLinePayAmount(model.getOnLinePayAmount());
        dto.setDiscountAmount(model.getDiscountAmount());
        dto.setMachineCode(model.getMachineCode());
        dto.setPayStatus(model.getPayStatus());
        dto.setOrderType(model.getOrderType());
        dto.setSid(model.getSid());
        dto.setParkId(model.getParkId());
        dto.setPlateNumber(model.getPlateNumber());
        dto.setUserId(model.getUserId());
        dto.setTripType(model.getTripType());
        dto.setBid(model.getBid());
        dto.setEtcPay(model.getEtcPay());
        dto.setParkName(model.getParkName());
        dto.setPromotionCode(model.getPromotionCode());
        dto.setExtendsFiles(model.getExtendsFiles());
        buildDisplay(dto);
        return dto;
    }

    /**
     * 页面对象转数据库对象
     *
     * @param dto 页面对象
     * @return 数据库对象
     */
    public UserStrokeOrder dtoToModel(UserStrokeOrderDTO dto) {
        if (null == dto) {
            return null;
        }
        final UserStrokeOrder model = new UserStrokeOrder();
        model.setOrderId(dto.getOrderId());
        model.setThirdPartyOrderId(dto.getThirdPartyOrderId());
        model.setStartTime(dto.getStartTime());
        model.setEndTime(dto.getEndTime());
        model.setNickName(dto.getNickName());
        model.setOrderAmount(dto.getOrderAmount());
        model.setPayAmount(dto.getPayAmount());
        model.setOnLinePayAmount(dto.getOnLinePayAmount());
        model.setDiscountAmount(dto.getDiscountAmount());
        model.setMachineCode(dto.getMachineCode());
        model.setPayStatus(dto.getPayStatus());
        model.setOrderType(dto.getOrderType());
        model.setSid(dto.getSid());
        model.setParkId(dto.getParkId());
        model.setPlateNumber(dto.getPlateNumber());
        model.setUserId(dto.getUserId());
        model.setTripType(dto.getTripType());
        model.setBid(dto.getBid());
        model.setEtcPay(dto.getEtcPay());
        model.setParkName(dto.getParkName());
        model.setPromotionCode(dto.getPromotionCode());
        model.setExtendsFiles(dto.getExtendsFiles());
        return model;
    }

    @SneakyThrows
    public void buildDisplay(OrderDTO dto) {
        if (null == dto.getServiceCharge()) {
            dto.setServiceCharge(new BigDecimal("0"));
        }
        if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_2)) {
            dto.setStationTypeDisplay(MbUtil.idToDisplay(dto.getStationType()));
        }
        if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_3)) {
            Optional.ofNullable(parkSpaceTenantService.getBaseMapper().spaceTenantById(dto.getOrderId())).ifPresent(orderSpaceTenantDTO -> {
                ObjectNode objectNode = JacksonUtil.getObjectMapper().createObjectNode();
                objectNode.put("spaceNumber", orderSpaceTenantDTO.getSpaceNumber());
                objectNode.put("sysSpaceNumber", orderSpaceTenantDTO.getSysSpaceNumber());
                dto.setParkInfo(objectNode);
            });
        }
        if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_4)) {
            UserStrokeOrderMallDTO mallDTO = userStrokeOrderMallService.getByIdDto(dto.getOrderId());
            if (null != mallDTO) {
                dto.setMall(objectMapper.readTree(objectMapper.writeValueAsString(mallDTO)));
            }
        }
        if (!StringUtils.hasLength(dto.getParkName()) && StringUtils.hasLength(dto.getParkIdDisplay())) {
            dto.setParkName(dto.getParkIdDisplay());
        }
        dto.setTripTypeDisplay(DisplayUtil.getTripTypeDisplay(dto.getTripType()));
        dto.setOrderAmountDisplay(Optional.ofNullable(dto.getOrderAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setPayAmountDisplay(Optional.ofNullable(dto.getPayAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setDiscountAmountDisplay(Optional.ofNullable(dto.getDiscountAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setServiceChargeDisplay(Optional.ofNullable(dto.getServiceCharge()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
    }

    /**
     * 码表转换
     *
     * @param dto 页面对象
     */
    public void buildDisplay(UserStrokeOrderDTO dto) {
        if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_1)) {
            Optional.ofNullable(userStrokeOrderChargeService.getByIdDto(dto.getOrderId())).ifPresent(userStrokeOrderCharge -> {
                if (StringUtils.hasLength(dto.getThirdPartyOrderId())) {
                    StatusChargeRespDTO statusCharge = null;
                    if (!Objects.equals(userStrokeOrderCharge.getState(), DisplayUtil.CHARGE_STAT_4)) {
                        //只要不是已结束或者为开始的，都需要充远端获得充电信息
                        final ChargeActionReqDTO chargeActionReqDTO = new ChargeActionReqDTO()
                                .setAction(ChargeActionReqDTO.ACTION_STATUS)
                                .setStartChargeSeq(dto.getThirdPartyOrderId())
                                .setConnectorID(userStrokeOrderCharge.getConnectorID())
                                .setOperatorID(userStrokeOrderCharge.getOperatorID());
                        statusCharge = dcService.statusCharge(dto.getParkId(), chargeActionReqDTO);
                        if (log.isDebugEnabled()) {
                            log.debug("查询充电状态,chargeActionReqDTO:{},statusCharge: {},数据库state: {}", chargeActionReqDTO, statusCharge, userStrokeOrderCharge.getState());
                        }
                    }
                    if (Objects.equals(userStrokeOrderCharge.getState(), DisplayUtil.CHARGE_STAT_3)) {
                        if (null != statusCharge) {
                            //只更新时间，为了计算充电时长，其他值都是取数据库的值。
                            log.error("充电状态是停止中的时候看下数据中台的返回值: {}", statusCharge);
                            String startTime = statusCharge.getStartTime();
                            String endTime = statusCharge.getEndTime();
                            statusCharge = new StatusChargeRespDTO();
                            statusCharge.setStartTime(startTime);
                            statusCharge.setEndTime(endTime);
                        }
                    }
                    if (null != statusCharge) {
                        if (null != statusCharge.getStartChargeSeqStat()) {
                            userStrokeOrderCharge.setState(statusCharge.getStartChargeSeqStat());
                            userStrokeOrderCharge.setStateDisplay(DisplayUtil.getChargeStatDisplay(statusCharge.getStartChargeSeqStat()));
                        }
                        if (StringUtils.hasLength(statusCharge.getStartTime())) {
                            userStrokeOrderCharge.setStartTime(DateUtil.strToLocalDateTime(statusCharge.getStartTime()));
                        }
                        if (StringUtils.hasLength(statusCharge.getEndTime())) {
                            userStrokeOrderCharge.setEndTime(DateUtil.strToLocalDateTime(statusCharge.getEndTime()));
                        }
                        if (null != statusCharge.getSoc()) {
                            userStrokeOrderCharge.setSoc(statusCharge.getSoc());
                        }
                        if (null != statusCharge.getTotalMoney()) {
                            userStrokeOrderCharge.setTotalAmount(new Money(statusCharge.getTotalMoney()).getAmount());
                        }
                        if (null != statusCharge.getElectricityMoney()) {
                            userStrokeOrderCharge.setElectricityFee(new Money(statusCharge.getElectricityMoney()).getAmount());
                        }
                        if (null != statusCharge.getTotalPower()) {
                            userStrokeOrderCharge.setChargeCapacity(new Money(statusCharge.getTotalPower()).getAmount());
                        }
                        if (null != statusCharge.getServiceMoney()) {
                            userStrokeOrderCharge.setServiceCharge(new Money(statusCharge.getServiceMoney()).getAmount());
                        }
                        //计算充电时长
                        final LocalDateTime startTime = Optional.ofNullable(userStrokeOrderCharge.getStartTime()).orElse(LocalDateTime.now());
                        final LocalDateTime endTime = Optional.ofNullable(userStrokeOrderCharge.getEndTime()).orElse(LocalDateTime.now());
                        final long chargeTime = Duration.between(startTime, endTime).toMinutes();
                        userStrokeOrderCharge.setChargeTime(chargeTime);
                        userStrokeOrderChargeService.buildDisplay(userStrokeOrderCharge);
                    }
                }
                Optional.ofNullable(userStrokeOrderCharge.getStartTime()).ifPresent(dto::setStartTime);
                if (null == dto.getEndTime()) {
                    Optional.ofNullable(userStrokeOrderCharge.getEndTime()).ifPresent(dto::setEndTime);
                }
                if (null == userStrokeOrderCharge.getChargeTime()) {
                    if (null != dto.getStartTime() && null != dto.getEndTime()) {
                        final long chargeTime = Duration.between(dto.getStartTime(), dto.getEndTime()).toMinutes();
                        userStrokeOrderCharge.setChargeTime(chargeTime);
                    }
                }
                Optional.ofNullable(userStrokeOrderCharge.getTotalAmount()).ifPresent(totalAmount -> {
                    dto.setOrderAmount(totalAmount);
                    dto.setPayAmount(totalAmount);
                    dto.setOnLinePayAmount(totalAmount);
                });
                dto.setOrderCharge(userStrokeOrderCharge);
            });
        } else if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_2)) {
            final UserStrokeOrderGasDTO userStrokeOrderGasDTO = userStrokeOrderGasService.getByIdDto(dto.getOrderId());
            if (null != userStrokeOrderGasDTO) {
                dto.setOrderGas(userStrokeOrderGasDTO);
            }
        } else if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_3)) {
            Optional.ofNullable(parkSpaceTenantService.getBaseMapper().spaceTenantById(dto.getOrderId())).ifPresent(dto::setSpaceTenant);
        } else if (dto.getTripType().equals(DisplayUtil.TRIP_TYPE_4)) {
            Optional.ofNullable(userStrokeOrderMallService.getByIdDto(dto.getOrderId())).ifPresent(dto::setOrderMall);
        }
        final List<UserStrokeOrderPreferentialLogDTO> list = userStrokeOrderPreferentialLogService.getCategoryListByOrderId(dto.getOrderId());
        if (!list.isEmpty()) {
            list.removeIf(userStrokeOrderPreferentialLogDTO -> userStrokeOrderPreferentialLogDTO.getFlag().equals(1));
            dto.setOrderPreferentialList(list);
            Map<String, UserStrokeOrderPreferentialLogDTO> map = new HashMap<>();
            for (UserStrokeOrderPreferentialLogDTO userStrokeOrderPreferentialLogDTO : list) {
                UserStrokeOrderPreferentialLogDTO userStrokeOrderPreferentialLogDTO1 = map.get(userStrokeOrderPreferentialLogDTO.getPreferentialCategory());
                if (null == userStrokeOrderPreferentialLogDTO1) {
                    userStrokeOrderPreferentialLogDTO1 = new UserStrokeOrderPreferentialLogDTO();
                    userStrokeOrderPreferentialLogDTO1.setPreferentialCategory(userStrokeOrderPreferentialLogDTO.getPreferentialCategory());
                    userStrokeOrderPreferentialLogDTO1.setPreferentialCategoryDisplay(userStrokeOrderPreferentialLogDTO.getPreferentialCategoryDisplay());
                    userStrokeOrderPreferentialLogDTO1.setDiscountAmount(userStrokeOrderPreferentialLogDTO.getDiscountAmount());
                    map.put(userStrokeOrderPreferentialLogDTO.getPreferentialCategory(), userStrokeOrderPreferentialLogDTO1);
                } else {
                    Money money = new Money(userStrokeOrderPreferentialLogDTO1.getDiscountAmount()).add(new Money(userStrokeOrderPreferentialLogDTO.getDiscountAmount()));
                    userStrokeOrderPreferentialLogDTO1.setDiscountAmount(money.getAmount());
                }

            }
            ArrayNode arrayNode = objectMapper.createArrayNode();
            for (String key : map.keySet()) {
                ObjectNode jsonNode = objectMapper.createObjectNode();
                UserStrokeOrderPreferentialLogDTO userStrokeOrderPreferentialLogDTO = map.get(key);
                jsonNode.put("discountAmount", userStrokeOrderPreferentialLogDTO.getDiscountAmount());
                jsonNode.put("discountAmountDisplay", BigDecimalUtil.buildDefaultScale(userStrokeOrderPreferentialLogDTO.getDiscountAmount()).stripTrailingZeros().toPlainString());
                if (null == userStrokeOrderPreferentialLogDTO.getServiceCharge()) {
                    userStrokeOrderPreferentialLogDTO.setServiceCharge(new BigDecimal("0"));
                }
                jsonNode.put("serviceCharge", userStrokeOrderPreferentialLogDTO.getServiceCharge());
                jsonNode.put("serviceChargeDisplay", BigDecimalUtil.buildDefaultScale(userStrokeOrderPreferentialLogDTO.getServiceCharge()).stripTrailingZeros().toPlainString());
                jsonNode.put("preferentialCategory", userStrokeOrderPreferentialLogDTO.getPreferentialCategory());
                jsonNode.put("preferentialCategoryDisplay", userStrokeOrderPreferentialLogDTO.getPreferentialCategoryDisplay());
                arrayNode.add(jsonNode);
            }
            dto.setPreferentialList(arrayNode);
        }
        if (null == dto.getServiceCharge()) {
            dto.setServiceCharge(new BigDecimal(0));
        }
        dto.setEtcPayDisplay(MbUtil.idToDisplay(dto.getEtcPay()));
        dto.setParkIdDisplay(carParkService.idToDisplay(dto.getParkId()));
        if (!StringUtils.hasLength(dto.getParkIdDisplay())) {
            dto.setParkIdDisplay(dto.getParkName());
        }
        dto.setTripTypeDisplay(DisplayUtil.getTripTypeDisplay(dto.getTripType()));
        dto.setOrderTypeDisplay(DisplayUtil.getOrderTypeDisplay(dto.getOrderType()));
        dto.setPayStatusDisplay(DisplayUtil.getPayStatusDisplay(dto.getPayStatus()));
        dto.setOrderAmountDisplay(Optional.ofNullable(dto.getOrderAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setPayAmountDisplay(Optional.ofNullable(dto.getPayAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setOnLinePayAmountDisplay(Optional.ofNullable(dto.getOnLinePayAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setDiscountAmountDisplay(Optional.ofNullable(dto.getDiscountAmount()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setServiceChargeDisplay(Optional.ofNullable(dto.getServiceCharge()).map(amount -> BigDecimalUtil.buildDefaultScale(amount).stripTrailingZeros().toPlainString()).orElse(""));
        dto.setPromotionCodeDisplay(MbUtil.idToDisplay(dto.getPromotionCode()));
        if (!StringUtils.hasLength(dto.getParkName())) {
            if (StringUtils.hasLength(dto.getParkIdDisplay())) {
                dto.setParkName(dto.getParkIdDisplay());
            }
        }
        if (dto.getPayStatus().equals(DisplayUtil.PAY_STATUS_1)) {
            //查询是否存在退款信息
            List<UserStrokeOrderRefundDTO> refundList = userStrokeOrderRefundService.getListDTO(dto.getOrderId());
            if (!refundList.isEmpty()) {
                dto.setRefundList(refundList);
            }
        }
    }

    /**
     * 数据库对象转页面对象
     *
     * @param list 数据库对象
     * @return 页面对象
     */
    public List<UserStrokeOrderDTO> modelToDtoList(List<UserStrokeOrder> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::modelToDto).collect(toList());
    }

    /**
     * 页面对象转数据库对象
     *
     * @param list 页面对象
     * @return 数据库对象
     */
    public List<UserStrokeOrder> dtoToModelList(List<UserStrokeOrderDTO> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::dtoToModel).collect(toList());
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto 查询参数，不为空的属性会被自动加入
     */
    public QueryWrapper<UserStrokeOrder> createQueryWrapper(UserStrokeOrderDTO dto) {
        return createQueryWrapper(dto, "");
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto        查询参数，不为空的属性会被自动加入
     * @param tableAlias 表别名
     */
    public QueryWrapper<UserStrokeOrder> createQueryWrapper(UserStrokeOrderDTO dto, String tableAlias) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(tableAlias)) {
                if (tableAlias.charAt(tableAlias.length() - 1) != '.') {
                    tableAlias += ".";
                }
            }
            if (StringUtils.hasLength(dto.getNickName())) {
                queryWrapper.eq(tableAlias + "nick_name", dto.getNickName());
            }
            if (StringUtils.hasLength(dto.getMachineCode())) {
                queryWrapper.eq(tableAlias + "machine_code", dto.getMachineCode());
            }
            if (StringUtils.hasLength(dto.getSid())) {
                queryWrapper.eq(tableAlias + "sid", dto.getSid());
            }
            if (StringUtils.hasLength(dto.getParkId())) {
                queryWrapper.eq(tableAlias + "park_id", dto.getParkId());
            }
            if (StringUtils.hasLength(dto.getPlateNumber())) {
                queryWrapper.eq(tableAlias + "plate_number", dto.getPlateNumber());
            }
            if (StringUtils.hasLength(dto.getUserId())) {
                queryWrapper.eq(tableAlias + "user_id", dto.getUserId());
            }
            if (null != dto.getPayAmount()) {
                queryWrapper.eq(tableAlias + "pay_amount", dto.getPayAmount());
            }
        }
        return queryWrapper;
    }

    /**
     * 批量检查主键在数据库中是否存在
     *
     * @param ids 主键集合
     */
    public List<UserStrokeOrder> checkIds(List<String> ids) {
        if (null == ids || ids.isEmpty()) {
            throw new ApiException("ids不能为空");
        }
        final List<UserStrokeOrder> list = super.listByIds(ids);
        if (list.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(ids.toArray()));
        }
        if (ids.size() == list.size()) {
            return list;
        }
        List<String> dbIds = list.stream().map(UserStrokeOrder::getOrderId).collect(toList());
        final List<String> notExistsList = ids.stream().filter(id -> !dbIds.contains(id)).collect(toList());
        dbIds.clear();
        if (!notExistsList.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(notExistsList.toArray()));
        }
        return list;
    }

    @Transactional(readOnly = true)
    public List<UserStrokeOrderDTO> getListByBid(String bid) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("bid", bid);
        queryWrapper.orderByAsc(START_TIME);
        final List<UserStrokeOrder> list = super.list(queryWrapper);
        queryWrapper.clear();
        if (null == list || list.isEmpty()) {
            return Collections.emptyList();
        }
        return modelToDtoList(list);
    }

    @Transactional(readOnly = true)
    public List<UserStrokeOrderDTO> getListBySid(String sid) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sid", sid);
        queryWrapper.in(PAY_STATUS, DisplayUtil.PAY_STATUS_1, DisplayUtil.PAY_STATUS_2, DisplayUtil.PAY_STATUS_3);
        queryWrapper.orderByAsc(START_TIME);
        final List<UserStrokeOrder> list = super.list(queryWrapper);
        queryWrapper.clear();
        if (null == list || list.isEmpty()) {
            return Collections.emptyList();
        }
        return modelToDtoList(list);
    }

    /**
     * 获得充电订单
     *
     * @param sid 行程ID
     * @return 充电订单列表
     */
    @Transactional(readOnly = true)
    public List<UserStrokeOrderDTO> getChargeListBySid(String sid) {
        final QueryWrapper<UserStrokeOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sid", sid);
        queryWrapper.eq("trip_type", DisplayUtil.TRIP_TYPE_1);
        queryWrapper.orderByAsc(START_TIME);
        final List<UserStrokeOrder> list = super.list(queryWrapper);
        queryWrapper.clear();
        if (null == list || list.isEmpty()) {
            return Collections.emptyList();
        }
        return modelToDtoList(list);
    }

    /**
     * 是否存在支付成功的停车订单
     *
     * @param sid 行程ID
     * @return true or false
     */
    @Transactional(readOnly = true)
    public boolean parkPaySuccess(String sid) {
        QueryWrapper<UserStrokeOrder> query = new QueryWrapper<>();
        query.eq("sid", sid);
        query.eq(PAY_STATUS, DisplayUtil.PAY_STATUS_1);
        query.eq("trip_type", DisplayUtil.TRIP_TYPE_0);
        final Long count = getBaseMapper().selectCount(query);
        return null != count && count > 0;
    }

    @Transactional(readOnly = true)
    public IPage<OrderDTO> userOrderPage(Page<OrderDTO> page, OrderQueryDTO orderQueryDTO) {
        QueryWrapper<OrderQueryDTO> queryWrapper = getQueryWrapper(orderQueryDTO);
        queryWrapper.orderByDesc("o.start_time");
        final IPage<OrderDTO> iPage = getBaseMapper().userOrderPage(page, queryWrapper);
        queryWrapper.clear();
        Optional.ofNullable(iPage.getRecords()).ifPresent(l -> l.forEach(this::buildDisplay));
        return iPage;
    }

    private QueryWrapper<OrderQueryDTO> getQueryWrapper(OrderQueryDTO orderQueryDTO) {
        final QueryWrapper<OrderQueryDTO> queryWrapper = new QueryWrapper<>();
        Integer month = orderQueryDTO.getMonth();
        if (null == month) {
            //默认查询一个月
            month = 1;
        }
        if (month < 1) {
            throw new ApiException("month异常");
        }
        if (month > 12) {
            throw new ApiException("只支持查询最近一年的数据");
        }
        final Date end = new Date();
        final DateTime start = cn.hutool.core.date.DateUtil.offsetMonth(end, -month);
        if (null != orderQueryDTO.getTripType()) {
            queryWrapper.eq("trip_type", orderQueryDTO.getTripType());
        }
        queryWrapper.eq("pay_status", DisplayUtil.PAY_STATUS_1);
        queryWrapper.eq("user_id", SecurityUtils.getLoginUser().getUserId());
        queryWrapper.between("start_time", start, end);
        return queryWrapper;
    }

    @Transactional(readOnly = true)
    public TotalPreferentialAmountDTO totalPreferentialAmount(OrderQueryDTO orderQueryDTO) {
        if (null == orderQueryDTO.getTripType()) {
            throw new ApiException("行程类型不能为空");
        }
        final QueryWrapper<OrderQueryDTO> queryWrapper = getQueryWrapper(orderQueryDTO);
        TotalPreferentialAmountDTO totalPreferentialAmount = Optional.ofNullable(baseMapper.totalPreferentialAmount(queryWrapper)).orElse(new TotalPreferentialAmountDTO());
        queryWrapper.clear();
        if (null == totalPreferentialAmount.getOrderAmount()) {
            totalPreferentialAmount.setOrderAmount(new BigDecimal("0.00"));
            totalPreferentialAmount.setOrderAmountDisplay("0");
        } else {
            totalPreferentialAmount.setOrderAmountDisplay(BigDecimalUtil.buildDefaultScale(totalPreferentialAmount.getOrderAmount()).stripTrailingZeros().toPlainString());
        }
        if (null == totalPreferentialAmount.getDiscountAmount()) {
            totalPreferentialAmount.setDiscountAmount(new BigDecimal("0.00"));
            totalPreferentialAmount.setDiscountAmountDisplay("0");
        } else {
            totalPreferentialAmount.setDiscountAmountDisplay(BigDecimalUtil.buildDefaultScale(totalPreferentialAmount.getDiscountAmount()).stripTrailingZeros().toPlainString());
        }

        return totalPreferentialAmount;
    }

    @Transactional(readOnly = true)
    public Long countGasOrder(String userId, String parkId, String gasStationCompanyName) {
        String nowDate = DateUtil.getStringNowDate();
        String start = nowDate + " 00:00:00";
        String end = nowDate + " 23:59:59";
        return countGasOrder(userId, parkId, gasStationCompanyName, start, end);
    }

    @Transactional(readOnly = true)
    public Long countGasOrderByMonth(String userId, String parkId, String gasStationCompanyName) {
        LocalDate date = LocalDate.now();
        //本月第一天
        LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth());
        //下个月的第一天
        LocalDate endDay = firstDay.plusMonths(1);

        String start = DateUtil.localDateToStr(firstDay) + " 00:00:00";
        String end = DateUtil.localDateToStr(endDay) + " 00:00:00";
        return countGasOrder(userId, parkId, gasStationCompanyName, start, end);
    }

    @Transactional(readOnly = true)
    public Long countGasOrder(String userId, String parkId, String gasStationCompanyName, String start, String end) {
        final QueryWrapper<Object> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("t1.user_id", userId);
        queryWrapper.eq("t1.pay_status", DisplayUtil.PAY_STATUS_1);
        queryWrapper.eq("t1.trip_type", DisplayUtil.TRIP_TYPE_2);
        if (gasStationCompanyName.equals(GasStationCompanyNameEnum.E_JIA_YOU.getCode())) {
            queryWrapper.eq("t1.park_id", parkId);
        }
        queryWrapper.between("t1.start_time", start, end);
        return SqlHelper.retCount(getBaseMapper().countGasOrder(queryWrapper));
    }
}